package parser

import (
	"bytes"
	"fmt"
	"strings"

	"gopkg.in/src-d/go-errors.v1"

	"github.com/shopspring/decimal"

	"github.com/spf13/cast"

	"vitess.io/vitess/go/vt/sqlparser"
)

func Parse(query string) (Statement, error) {
	sql := strings.TrimSpace(query)
	stmt, err := sqlparser.Parse(sql)
	if err != nil {
		return nil, errors.NewKind("%s").New(err.Error())
	}
	return convert(stmt, sql)
}

func convert(stmt sqlparser.Statement, query string) (Statement, error) {
	switch n := stmt.(type) {
	case *sqlparser.Select:
		return convertSelect(n, query)
	case *sqlparser.Insert:
		return convertInsert(n, query)
	case *sqlparser.Delete:
		return convertDelete(n, query)
	case *sqlparser.Update:
		return convertUpdate(n, query)
	case *sqlparser.Show:
		return convertShow(n, query)
	case *sqlparser.Set:
		return convertSet(n, query)
	case *sqlparser.Use:
		return convertUse(n, query)
	case *sqlparser.ExplainTab:
		return convertExplainTab(n, query)
	case sqlparser.DDLStatement:
		ddl, err := sqlparser.ParseStrictDDL(query)
		if err != nil {
			return nil, err
		}
		return convertDDL(ddl.(sqlparser.DDLStatement), query)
	default:
		return nil, fmt.Errorf("unsupported syntax: %#v", n)
	}
}

func tableExprsToTables(te sqlparser.TableExprs) ([]*TableItem, error) {
	if len(te) == 0 {
		return nil, fmt.Errorf("zero tables in FROM")
	}
	var tables []*TableItem
	for _, t := range te {
		table, err := tableExprToTable(t)
		if err != nil {
			return nil, err
		}
		tables = append(tables, table)
	}
	return tables, nil
}

func tableExprToTable(te sqlparser.TableExpr) (*TableItem, error) {
	tableItem := &TableItem{}
	switch t := (te).(type) {
	case *sqlparser.AliasedTableExpr:
		switch e := t.Expr.(type) {
		case sqlparser.TableName:
			tableItem.Qualifier = e.Qualifier.String()
			tableItem.Name = e.Name.String()
			return tableItem, nil
		case sqlparser.SelectStatement:
			return nil, fmt.Errorf("unsupported syntax: %#v", te)
		default:
			return nil, fmt.Errorf("unsupported syntax: %#v", te)
		}
	case *sqlparser.JoinTableExpr:
		return nil, fmt.Errorf("unsupported syntax: %#v", te)
	default:
		return nil, fmt.Errorf("unsupported syntax: %#v", te)
	}
}

func whereToWhereExpr(w *sqlparser.Where) (*WhereExpr, error) {
	where := NewWhereExpr()
	var buf bytes.Buffer
	err := exprToString(&buf, w.Expr)
	if err != nil {
		return nil, err
	}
	err = where.setExpr(buf.String())
	if err != nil {
		return nil, err
	}
	return where, nil
}

func colNameToColumn(col *sqlparser.ColName) *Column {
	c := &Column{}
	switch col.Name.AtCount() {
	case 0:
		c.typ = FieldType_DataColumn
	case 1:
		c.typ = FieldType_UserVariable
	case 2:
		c.typ = FieldType_SystemVariable
	}
	c.tbl = col.Qualifier.Name.String()
	c.db = col.Qualifier.Qualifier.String()
	c.expr = col.Name.String()
	if c.tbl != "" {
		c.expr = c.tbl + "." + c.GetExpr()
		if c.db != "" {
			c.expr = c.db + "." + c.expr
		}
	}
	variName := col.Name.String()[strings.LastIndex(col.Name.String(), "@")+1:]
	c.name = variName[strings.LastIndex(variName, ".")+1:]
	return c
}

func LiteralToValue(val *sqlparser.Literal) (interface{}, error) {
	switch val.Type {
	case sqlparser.IntVal:
		return cast.ToInt(val.Val), nil
	case sqlparser.DecimalVal:
		return decimal.NewFromString(val.Val)
	case sqlparser.FloatVal:
		return cast.ToFloat64(val.Val), nil
	default:
		return cast.ToString(val.Val), nil
	}
}

//func exprAnalysis(columns ColumnSet, expr sqlparser.SQLNode) ([]sqlparser.Expr, error) {
//	switch v := expr.(type) {
//	case *sqlparser.ColName:
//		//col := colNameToColumn(v)
//		//columns.Add(col)
//		return []sqlparser.Expr{v}, nil
//	case *sqlparser.FuncExpr:
//		for _, e := range v.Exprs {
//			aliasedExpr, ok := e.(*sqlparser.AliasedExpr)
//			if ok {
//				_, err := exprAnalysis(columns, aliasedExpr.Expr)
//				if err != nil {
//					return nil, err
//				}
//			}
//		}
//		return []sqlparser.Expr{v}, nil
//	case *sqlparser.Literal:
//		return []sqlparser.Expr{v}, nil
//	case *sqlparser.ComparisonExpr:
//		lexprs, err := exprAnalysis(columns, v.Left)
//		if err != nil {
//			return nil, err
//		}
//		rexprs, err := exprAnalysis(columns, v.Right)
//		if err != nil {
//			return nil, err
//		}
//		switch v.Operator {
//		case sqlparser.InOp:
//			//for _, v := range rexprs {
//			//	//val, err := LiteralToValue(v.(*sqlparser.Literal))
//			//	//if err != nil {
//			//	//	return nil, err
//			//	//}
//			//	col := colNameToColumn(lexprs[0].(*sqlparser.ColName))
//			//	////col.value = val
//			//	columns.Add(col)
//			//}
//		case sqlparser.EqualOp:
//			colName, ok := lexprs[0].(*sqlparser.ColName)
//			if ok {
//				col := colNameToColumn(colName)
//				//val, err := LiteralToValue(rexprs[0].(*sqlparser.Literal))
//				if err != nil {
//					return nil, err
//				}
//				//col.value = val
//				columns.Add(col)
//			}
//		default:
//			colName, ok := lexprs[0].(*sqlparser.ColName)
//			if ok {
//				col := colNameToColumn(colName)
//				columns.Add(col)
//			}
//		}
//		return []sqlparser.Expr{v}, nil
//	case *sqlparser.AndExpr:
//		lexprs, err := exprAnalysis(columns, v.Left)
//		if err != nil {
//			return nil, err
//		}
//		rexprs, err := exprAnalysis(columns, v.Right)
//		if err != nil {
//			return nil, err
//		}
//		var exprs []sqlparser.Expr
//		for _, lexpr := range lexprs {
//			for _, rexpr := range rexprs {
//				exprs = append(exprs, &sqlparser.AndExpr{
//					Left:  lexpr,
//					Right: rexpr,
//				})
//			}
//		}
//		return exprs, nil
//	case *sqlparser.OrExpr:
//		lexprs, err := exprAnalysis(columns, v.Left)
//		if err != nil {
//			return nil, err
//		}
//		rexprs, err := exprAnalysis(columns, v.Right)
//		if err != nil {
//			return nil, err
//		}
//		var exprs []sqlparser.Expr
//		for _, lexpr := range lexprs {
//			exprs = append(exprs, lexpr)
//		}
//		for _, rexpr := range rexprs {
//			exprs = append(exprs, rexpr)
//		}
//		return exprs, nil
//	case sqlparser.ValTuple:
//		return v, nil
//	default:
//		return nil, fmt.Errorf("unsupported syntax: %#v", expr)
//	}
//}

func selectExprsToFields(se sqlparser.SelectExprs) ([]*Field, error) {
	fields := make([]*Field, len(se))
	for i, e := range se {
		field, err := selectExprToField(e)
		if err != nil {
			return fields, err
		}
		fields[i] = field
	}
	return fields, nil
}

func SQLNodeToColumns(columns ColumnSet, node sqlparser.SQLNode) error {
	switch expr := node.(type) {
	case *sqlparser.Literal:
		return nil
	case *sqlparser.ColName:
		col := colNameToColumn(expr)
		columns.Add(col)
		return nil
	case *sqlparser.AliasedExpr:
		return SQLNodeToColumns(columns, expr.Expr)
	case *sqlparser.FuncExpr:
		for _, e := range expr.Exprs {
			err := SQLNodeToColumns(columns, e)
			if err != nil {
				return err
			}
		}
		return nil
	case *sqlparser.BinaryExpr:
		err := SQLNodeToColumns(columns, expr.Left)
		if err != nil {
			return err
		}
		return SQLNodeToColumns(columns, expr.Right)
	case *sqlparser.ComparisonExpr:
		err := SQLNodeToColumns(columns, expr.Left)
		if err != nil {
			return err
		}
		err = SQLNodeToColumns(columns, expr.Right)
		if err != nil {
			return err
		}
		switch expr.Operator {
		case sqlparser.InOp:
			colName, ok := expr.Left.(*sqlparser.ColName)
			if ok {
				name := colName.Name.String()
				values := expr.Right.(sqlparser.ValTuple)
				for _, v := range values {
					val, err := LiteralToValue(v.(*sqlparser.Literal))
					if err != nil {
						return err
					}
					columns.Get(name).AddValue(val)
				}
			}
			return nil
		case sqlparser.EqualOp:
			colName, ok_left := expr.Left.(*sqlparser.ColName)
			literal, ok_right := expr.Right.(*sqlparser.Literal)
			if ok_left && ok_right {
				val, err := LiteralToValue(literal)
				if err != nil {
					return err
				}
				c := columns.Get(colName.Name.String())
				c.AddValue(val)
			}
			return nil
		default:
			return nil
		}
	case sqlparser.SelectExprs:
		for _, e := range expr {
			err := SQLNodeToColumns(columns, e)
			if err != nil {
				return err
			}
		}
		return nil
	case *sqlparser.Select:
		for _, e := range expr.SelectExprs {
			err := SQLNodeToColumns(columns, e)
			if err != nil {
				return err
			}
		}
		return SQLNodeToColumns(columns, expr.Where)
	case *sqlparser.Delete:
		return SQLNodeToColumns(columns, expr.Where)
	case *sqlparser.Update:
		err := SQLNodeToColumns(columns, expr.Exprs)
		if err != nil {
			return err
		}
		return SQLNodeToColumns(columns, expr.Where)
	case sqlparser.UpdateExprs:
		for _, e := range expr {
			err := SQLNodeToColumns(columns, e)
			if err != nil {
				return err
			}
		}
		return nil
	case *sqlparser.UpdateExpr:
		err := SQLNodeToColumns(columns, expr.Name)
		if err != nil {
			return err
		}
		return SQLNodeToColumns(columns, expr.Expr)
	case *sqlparser.Where:
		if expr == nil {
			return nil
		}
		return SQLNodeToColumns(columns, expr.Expr)
	case *sqlparser.AndExpr:
		err := SQLNodeToColumns(columns, expr.Left)
		if err != nil {
			return err
		}
		return SQLNodeToColumns(columns, expr.Right)
	case *sqlparser.OrExpr:
		err := SQLNodeToColumns(columns, expr.Left)
		if err != nil {
			return err
		}
		return SQLNodeToColumns(columns, expr.Right)
	case sqlparser.ValTuple:
		for _, e := range expr {
			err := SQLNodeToColumns(columns, e)
			if err != nil {
				return err
			}
		}
		return nil
	case *sqlparser.StarExpr:
		return fmt.Errorf("unsupported syntax: 'select *' is not supported, please specify the columns")
	default:
		return fmt.Errorf("unsupported syntax: %#v", expr)
	}
}

func SQLNodeToString(expr sqlparser.SQLNode) string {
	buf := sqlparser.NewTrackedBuffer(nil)
	expr.Format(buf)
	return buf.String()
}
